package jia.game.combat;

import jia.game.GenericFlags;
import org.apache.log4j.Logger;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferStrategy;
import java.util.ArrayList;

public class CombatFrame extends JFrame implements Runnable, KeyListener, MouseListener, MouseMotionListener, FocusListener, CombatEventProcessable{
    private Thread loop;
    private GraphicsDevice graphicsDevice;
    private int DISPLAY_X = 0;
    private int DISPLAY_Y = 0;
    private BufferStrategy strategy;
    private static final int FULLSCREEN_MODE = 0;
    private static final int WINDOWED_MODE = 1;
    private CombatEventProcessor combatEventProcessor;
    private static final Logger log = Logger.getLogger(CombatFrame.class);
    
    
    public CombatFrame(GraphicsDevice graphicsDevice){
        super(graphicsDevice.getDefaultConfiguration());
        this.graphicsDevice = graphicsDevice;
        getContentPane().setLayout(null);
        setIgnoreRepaint(false);
        setResizable(false);
        addKeyListener(this);
        getContentPane().addMouseListener(this);
        getContentPane().addMouseMotionListener(this);
        addFocusListener(this);
        combatEventProcessor = new CombatEventProcessor(this);
        CombatGlobals.framework = this;
        
        // setup mouse and keyboard
        CombatGlobals.keyboard = new Keyboard();
        CombatGlobals.mouse = new Mouse();
    }
    
    public void setMode(int mode){
        if(mode == FULLSCREEN_MODE) {
            if(!graphicsDevice.isFullScreenSupported()) {
                mode = WINDOWED_MODE;
                if (GenericFlags.debugMessages){
                    log.debug("Sorry, fullscreen mode not supported, continuing in windowed mode");
                }
            }
        }
        try {
            if(mode == FULLSCREEN_MODE) {
                setUndecorated(true);
                graphicsDevice.setFullScreenWindow(this);
                if(graphicsDevice.isDisplayChangeSupported()) {
                    DisplayMode dm = new DisplayMode(CombatGlobals.DISPLAY_WIDTH, CombatGlobals.DISPLAY_HEIGHT, 16, DisplayMode.REFRESH_RATE_UNKNOWN);
                    if(isDisplayModeAvailable(dm)) {
                        graphicsDevice.setDisplayMode(dm);
                    } else {
                        if (GenericFlags.debugMessages){
                            log.debug("Display mode not available: " + dm.getWidth() + ":" + dm.getHeight() + ":" + dm.getBitDepth());
                        }
                        System.exit(0);
                    }
                } else {
                    if (GenericFlags.debugMessages){
                        log.debug("Display change not supported");
                    }
                    System.exit(0);
                }
            } else {
                // WINDOWED_MODE
                setTitle(CombatGlobals.WINDOW_TITLE);
                setVisible(true);
                Insets insets = getInsets();
                DISPLAY_X = insets.left;
                DISPLAY_Y = insets.top;
                resizeToInternalSize(CombatGlobals.DISPLAY_WIDTH, CombatGlobals.DISPLAY_HEIGHT);
            }
            createBufferStrategy(3);
            strategy = getBufferStrategy();
        } catch(Exception e) {
            graphicsDevice.setFullScreenWindow(null);
            if (GenericFlags.errorMessages){
                log.error(e.getMessage());
            }
        }
        if(!strategy.getCapabilities().isPageFlipping()) {
            if (GenericFlags.debugMessages){
                log.debug("Page flipping is not available in this mode");
            }
        }
        waitForReadyStrategy();
    }
    
    public void resizeToInternalSize(int internalWidth, int internalHeight){
        Insets insets = getInsets();
        final int newWidth = internalWidth + insets.left + insets.right;
        final int newHeight = internalHeight + insets.top + insets.bottom;
        Runnable resize = new Runnable(){
            public void run(){
                setSize(newWidth, newHeight);
            }
        };
        if(!SwingUtilities.isEventDispatchThread()) {
            try {
                SwingUtilities.invokeAndWait(resize);
            } catch(Exception e) {
                if (GenericFlags.errorMessages){
                    log.error(e.getMessage());
                }
            }
        } else {
            resize.run();
        }
        validate();
    }
    
    public boolean isDisplayModeAvailable(DisplayMode dm){
        DisplayMode[] availableModes = graphicsDevice.getDisplayModes();
        for(DisplayMode availableMode : availableModes) {
            if(dm.getWidth() == availableMode.getWidth() && dm.getHeight() == availableMode.getHeight() && dm.getBitDepth() == availableMode.getBitDepth()) {
                return true;
            }
        }
        return false;
    }
    
    public void waitForReadyStrategy(){
        int iterations = 0;
        while(true) {
            try {
                Thread.sleep(20);
            } catch(InterruptedException e) {
                if (GenericFlags.errorMessages){
                    log.error(e.getMessage());
                }
            }
            try {
                strategy.getDrawGraphics();
                break;
            } catch(IllegalStateException e) {
                if (GenericFlags.debugMessages){
                    log.debug("BufferStrategy not ready yet");
                }
            }
            iterations++;
            if(iterations == 100) {
                //	(Unlikely event) No use after 2 seconds (100*20ms = 2secs) give up trying
                if (GenericFlags.debugMessages){
                    log.debug("Exiting Program, unable to use BufferStrategy");
                }
                System.exit(0);
            }
        }
    }
    
    public void start(){
        loop = new Thread(this);
        loop.start();
    }
    
    public void run(){
        long startTime, waitTime, elapsedTime;
        //	1000/25 Frames Per Second = 40millisecond delay
        int delayTime = 1000 / 25;
        Thread thisThread = Thread.currentThread();
        while(loop == thisThread) {
            startTime = System.currentTimeMillis();
            combatEventProcessor.processEventList();
            CombatGlobals.currentScreen.process();
            Graphics g = strategy.getDrawGraphics();
            if(!strategy.contentsLost()) {
                g.translate(DISPLAY_X, DISPLAY_Y);
                CombatGlobals.currentScreen.render(g);
                g.dispose();
                strategy.show();
            }
            
            //  handle frame rate
            elapsedTime = System.currentTimeMillis() - startTime;
            waitTime = Math.max(delayTime - elapsedTime, 5);
            try {
                Thread.sleep(waitTime);
            } catch(InterruptedException e) {
                if (GenericFlags.errorMessages){
                    log.error(e.getMessage());
                }
            }
        }
        if (GenericFlags.debugMessages){
            log.debug("Program Exited");
        }
        dispose();
        System.exit(0);
    }
    
    public void exitProgram(){
        loop = null;
    }
    
    public void initGame(ArrayList combatants){
        // create your screens...
        CombatGlobals.combatGrid = new CombatGrid(combatants);
        
        // set the current (starting) screen...
        CombatGlobals.currentScreen = CombatGlobals.previousScreen = CombatGlobals.combatGrid;
        CombatGlobals.currentScreen.load();
    }
    
    public boolean handleGlobalEvent(AWTEvent e){
        // handle global events...
        switch(e.getID()) {
            case KeyEvent.KEY_PRESSED:
                KeyEvent keyEvent = (KeyEvent) e;
                CombatGlobals.keyboard.keyState[keyEvent.getKeyCode()] = true;
                switch(keyEvent.getKeyCode()) {
                    case KeyEvent.VK_ESCAPE:
                        exitProgram();
                        return true;
                }
                break;
            case KeyEvent.KEY_RELEASED:
                CombatGlobals.keyboard.keyState[((KeyEvent) e).getKeyCode()] = false;
                break;
            case MouseEvent.MOUSE_MOVED:
            case MouseEvent.MOUSE_DRAGGED: {
                MouseEvent mouseEvent = (MouseEvent) e;
                CombatGlobals.mouse.x = mouseEvent.getX();
                CombatGlobals.mouse.y = mouseEvent.getY();
                break;
            }
            case MouseEvent.MOUSE_PRESSED:
                switch(((MouseEvent) e).getButton()) {
                    case MouseEvent.BUTTON1:
                        CombatGlobals.mouse.button[Mouse.LEFT_BUTTON] = true;
                        break;
                    case MouseEvent.BUTTON2:
                        CombatGlobals.mouse.button[Mouse.MIDDLE_BUTTON] = true;
                        break;
                    case MouseEvent.BUTTON3:
                        CombatGlobals.mouse.button[Mouse.RIGHT_BUTTON] = true;
                        break;
                }
                break;
            case MouseEvent.MOUSE_RELEASED:
                switch(((MouseEvent) e).getButton()) {
                    case MouseEvent.BUTTON1:
                        CombatGlobals.mouse.button[Mouse.LEFT_BUTTON] = false;
                        break;
                    case MouseEvent.BUTTON2:
                        CombatGlobals.mouse.button[Mouse.MIDDLE_BUTTON] = false;
                        break;
                    case MouseEvent.BUTTON3:
                        CombatGlobals.mouse.button[Mouse.RIGHT_BUTTON] = false;
                        break;
                }
                break;
            case FocusEvent.FOCUS_LOST:
                // reset key states...
                CombatGlobals.keyboard.resetAllStates();
                
                // reset mouse button states...
                CombatGlobals.mouse.resetAllStates();
                break;
            case FocusEvent.FOCUS_GAINED:
                break;
        }
        return false;
    }
    
    public void handleEvent(AWTEvent e){
        if(!handleGlobalEvent(e)) {
            CombatGlobals.currentScreen.handleEvent(e);
        }
    }
    
    public void setCurrentScreen(TemplateScreen screen){
        // unload the current screen...
        CombatGlobals.currentScreen.unload();
        
        // set this screen to the previous screen...
        CombatGlobals.previousScreen = CombatGlobals.currentScreen;
        
        // assign the new screen...
        CombatGlobals.currentScreen = screen;
        
        // load it...
        CombatGlobals.currentScreen.load();
    }
    
    // key listener methods
    public void keyPressed(KeyEvent e){
        combatEventProcessor.addEvent(e);
    }
    
    public void keyReleased(KeyEvent e){
        combatEventProcessor.addEvent(e);
    }
    
    public void keyTyped(KeyEvent e){
    }
    
    // mouse listener methods
    public void mousePressed(MouseEvent e){
        combatEventProcessor.addEvent(e);
    }
    
    public void mouseReleased(MouseEvent e){
        combatEventProcessor.addEvent(e);
    }
    
    public void mouseClicked(MouseEvent e){
    }
    
    public void mouseEntered(MouseEvent e){
    }
    
    public void mouseExited(MouseEvent e){
    }
    
    // mouse motion listener methods
    public void mouseMoved(MouseEvent e){
        combatEventProcessor.addEvent(e);
    }
    
    public void mouseDragged(MouseEvent e){
        combatEventProcessor.addEvent(e);
    }
    
    // focus listener methods
    public void focusGained(FocusEvent e){
        combatEventProcessor.addEvent(e);
    }
    
    public void focusLost(FocusEvent e){
        combatEventProcessor.addEvent(e);
    }
    
    public static void main(String args[]){
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        CombatFrame mainAppFrame = new CombatFrame(ge.getDefaultScreenDevice());
        Object[] options = {"FullScreen Mode", "Windowed Mode"};
        int choice = JOptionPane.showOptionDialog(null, "Select Display Mode:", "Display Mode", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
        if(choice != JOptionPane.CLOSED_OPTION) {
            //	choice will be either 0 or 1 corresponding to our mode flags
            // FULLSCREEN_MODE = 0, WINDOWED_MODE = 1
            
            // initialize and start the game...
            mainAppFrame.initGame(new ArrayList());
            mainAppFrame.setMode(choice);
            mainAppFrame.start();
        } else {
            System.exit(0);
        }
    }
}
